C.S.Burner 🪙 ━►🔥GIT Node
Node / reticulum_mirror / MeshChatX / files / meshchatx / src / frontend / components / blocked / BlockedPage.vue
Displaying Raw • Download
meshchatx/src/frontend/components/blocked/BlockedPage.vue 65ca9ddc823fcc2c8f8653c553b944b85db0875f (65ca9ddc) Text, 26.50 KB
T8b949e<!-- SPDX-License-Identifier: 0BSD -->
Tff7b72<templateTff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex min-w-0 flex-1 flex-col overflow-hidden bg-sem-canvas text-sem-fgTa5d6ff"Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex-1 overflow-x-hidden overflow-y-auto px-3 py-3 sm:px-5 sm:py-4 min-w-0Ta5d6ff"Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffmx-auto w-full max-w-6xl min-w-0 space-y-0 xl:max-w-7xlTa5d6ff"Tff7b72>
Tff7b72<div
Te6edf3class=Ta5d6ff"Ta5d6ffflex w-full flex-col gap-3 border-b border-sem-border py-3 sm:flex-row sm:items-start sm:justify-between sm:py-4Ta5d6ff"
Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffmin-w-0 space-y-1Ta5d6ff"Tff7b72>
Tff7b72<h1 Te6edf3class=Ta5d6ff"Ta5d6fftext-xl font-semibold tracking-tight sm:text-2xlTa5d6ff"Tff7b72>
{{ Te6edf3$t("banishment.title") }}
Tff7b72</h1>
Tff7b72<p Te6edf3class=Ta5d6ff"Ta5d6fftext-sm text-sem-fg-mutedTa5d6ff"Tff7b72>
{{ Te6edf3$t("banishment.description") }}
Tff7b72</p>
Tff7b72</div>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex items-center gap-2 sm:shrink-0Ta5d6ff"Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffrelative min-w-0 flex-1 sm:w-64 lg:w-80Ta5d6ff"Tff7b72>
Tff7b72<MaterialDesignIcon
Te6edf3icon-name=Ta5d6ff"Ta5d6ffmagnifyTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffpointer-events-none absolute left-2.5 top-1/2 z-10 size-4 shrink-0 -translate-y-1/2 text-sem-fg-mutedTa5d6ff"
Tff7b72/>
Tff7b72<input
Te6edf3v-model=Ta5d6ff"Ta5d6ffsearchQueryTa5d6ff"
Te6edf3type=Ta5d6ff"Ta5d6fftextTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffw-full rounded-full border-0 bg-sem-surface-muted py-2 pl-9 pr-3 text-sm text-sem-fg outline-none ring-1 ring-sem-border/50 focus:ring-sem-accent/40Ta5d6ff"
Te6edf3:placeholder=Ta5d6ff"Te6edf3$tTa5d6ff(Ta5d6ff'Ta5d6ffbanishment.search_placeholderTa5d6ff'Ta5d6ff)Ta5d6ff"
@Te6edf3input=Ta5d6ff"Ta5d6ffonSearchInputTa5d6ff"
Tff7b72/>
Tff7b72</div>
Tff7b72<button
Te6edf3v-if=Ta5d6ff"Ta5d6ff!selectModeTa5d6ff"
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffshrink-0 rounded-lg px-2 py-2 text-xs font-medium text-sem-fg-muted transition-colors hover:bg-sem-surface/60 hover:text-sem-fgTa5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffselectMode = trueTa5d6ff"
Tff7b72>
{{ Te6edf3$t("common.select") }}
Tff7b72</button>
Tff7b72<template v-elseTff7b72>
Tff7b72<label Te6edf3class=Ta5d6ff"Ta5d6ffflex shrink-0 cursor-pointer items-center gap-2Ta5d6ff"Tff7b72>
Tff7b72<input
Te6edf3type=Ta5d6ff"Ta5d6ffcheckboxTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffrounded border-sem-border text-sem-accent focus:ring-sem-accent/30Ta5d6ff"
Te6edf3:checked=Ta5d6ff"Ta5d6ffisAllSelectedTa5d6ff"
@Te6edf3change=Ta5d6ff"Ta5d6fftoggleSelectAllTa5d6ff"
Tff7b72/>
Tff7b72<span Te6edf3class=Ta5d6ff"Ta5d6fftext-xs text-sem-fg-mutedTa5d6ff"Tff7b72>{{ Te6edf3$t("archives.select_all") }}Tff7b72</span>
Tff7b72</label>
Tff7b72<button
Te6edf3v-if=Ta5d6ff"Ta5d6ffselectedIdentities.length > 0Ta5d6ff"
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffshrink-0 rounded-lg px-2 py-2 text-xs font-medium text-sem-accent transition-colors hover:bg-sem-surface/60Ta5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffonUnblockSelectedTa5d6ff"
Tff7b72>
{{ Te6edf3$t("banishment.lift_selected", { count: selectedIdentities.length }) }}
Tff7b72</button>
Tff7b72<button
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffshrink-0 rounded-lg px-2 py-2 text-xs font-medium text-sem-fg-muted transition-colors hover:bg-sem-surface/60 hover:text-sem-fgTa5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffexitSelectModeTa5d6ff"
Tff7b72>
{{ Te6edf3$t("common.cancel") }}
Tff7b72</button>
Tff7b72</template>
Tff7b72<button
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffinline-flex size-9 shrink-0 items-center justify-center rounded-[10px] text-sem-fg-muted transition hover:bg-sem-surface/60 hover:text-sem-fgTa5d6ff"
Te6edf3:title=Ta5d6ff"Te6edf3$tTa5d6ff(Ta5d6ff'Ta5d6ffcommon.refreshTa5d6ff'Ta5d6ff)Ta5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffloadBlockedDestinationsTa5d6ff"
Tff7b72>
Tff7b72<MaterialDesignIcon
Te6edf3icon-name=Ta5d6ff"Ta5d6ffrefreshTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffsize-4Ta5d6ff"
Te6edf3:class=Ta5d6ff"Ta5d6ff{ Ta5d6ff'Ta5d6ffanimate-spin-reverseTa5d6ff'Ta5d6ff: isLoading }Ta5d6ff"
Tff7b72/>
Tff7b72</button>
Tff7b72</div>
Tff7b72</div>
Tff7b72<div
Te6edf3v-if=Ta5d6ff"Ta5d6ff!isLoading || filteredBlockedIdentities.length > 0Ta5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffflex flex-wrap items-center gap-2 border-b border-sem-border/40 py-2Ta5d6ff"
Tff7b72>
Tff7b72<select
Te6edf3v-model=Ta5d6ff"Ta5d6fftypeFilterTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffrounded-full border-0 bg-sem-surface-muted px-3 py-1.5 text-xs text-sem-fg outline-none ring-1 ring-sem-border/50 focus:ring-sem-accent/40Ta5d6ff"
Tff7b72>
Tff7b72<option Te6edf3value=Ta5d6ff"Ta5d6ffallTa5d6ff"Tff7b72>{{ Te6edf3$t("banishment.filter_all_types") }}Tff7b72</option>
Tff7b72<option Te6edf3value=Ta5d6ff"Ta5d6ffuserTa5d6ff"Tff7b72>{{ Te6edf3$t("banishment.user") }}Tff7b72</option>
Tff7b72<option Te6edf3value=Ta5d6ff"Ta5d6ffnodeTa5d6ff"Tff7b72>{{ Te6edf3$t("banishment.node") }}Tff7b72</option>
Tff7b72<option Te6edf3value=Ta5d6ff"Ta5d6ffrnsTa5d6ff"Tff7b72>{{ Te6edf3$t("banishment.filter_rns") }}Tff7b72</option>
Tff7b72</select>
Tff7b72<select
Te6edf3v-model=Ta5d6ff"Ta5d6ffdateSortTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffrounded-full border-0 bg-sem-surface-muted px-3 py-1.5 text-xs text-sem-fg outline-none ring-1 ring-sem-border/50 focus:ring-sem-accent/40Ta5d6ff"
Tff7b72>
Tff7b72<option Te6edf3value=Ta5d6ff"Ta5d6ffnewestTa5d6ff"Tff7b72>{{ Te6edf3$t("banishment.sort_newest") }}Tff7b72</option>
Tff7b72<option Te6edf3value=Ta5d6ff"Ta5d6ffoldestTa5d6ff"Tff7b72>{{ Te6edf3$t("banishment.sort_oldest") }}Tff7b72</option>
Tff7b72<option Te6edf3value=Ta5d6ff"Ta5d6ffnameTa5d6ff"Tff7b72>{{ Te6edf3$t("banishment.sort_name") }}Tff7b72</option>
Tff7b72</select>
Tff7b72<span Te6edf3v-if=Ta5d6ff"Ta5d6fffilteredBlockedIdentities.length > 0Ta5d6ff" Te6edf3class=Ta5d6ff"Ta5d6fftext-xs text-sem-fg-mutedTa5d6ff"Tff7b72>
{{ Te6edf3$t("banishment.result_count", { count: filteredBlockedIdentities.length }) }}
Tff7b72</span>
Tff7b72</div>
Tff7b72<template Te6edf3v-if=Ta5d6ff"Ta5d6ffisLoading && filteredBlockedIdentities.length === 0Ta5d6ff"Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffgrid grid-cols-1 gap-3 py-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4Ta5d6ff"Tff7b72>
Tff7b72<div Te6edf3v-for=Ta5d6ff"Ta5d6ffi in 5Ta5d6ff" Te6edf3:key=Ta5d6ff"Ta5d6ff'Ta5d6ffskel-Ta5d6ff'Ta5d6ff + iTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffmin-h-[9.5rem] rounded-xl bg-sem-surface/40 p-3Ta5d6ff"Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex items-start gap-2Ta5d6ff"Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffsize-8 shrink-0 animate-pulse bg-sem-surface-mutedTa5d6ff" Tff7b72/>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffmin-w-0 flex-1 space-y-2Ta5d6ff"Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffh-3.5 w-28 animate-pulse rounded-sm bg-sem-surface-mutedTa5d6ff" Tff7b72/>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffh-3 w-full animate-pulse rounded-sm bg-sem-surface-muted/70Ta5d6ff" Tff7b72/>
Tff7b72</div>
Tff7b72</div>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffmt-3 h-8 animate-pulse bg-sem-surface-muted/70Ta5d6ff" Tff7b72/>
Tff7b72</div>
Tff7b72</div>
Tff7b72</template>
Tff7b72<div
Te6edf3v-else-if=Ta5d6ff"Ta5d6fffilteredBlockedIdentities.length === 0Ta5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffflex flex-col items-center justify-center py-16 text-center sm:py-20Ta5d6ff"
Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffmb-4 rounded-full bg-sem-surface-muted p-4 text-sem-fg-mutedTa5d6ff"Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffcheck-circleTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffsize-10Ta5d6ff" Tff7b72/>
Tff7b72</div>
Tff7b72<h3 Te6edf3class=Ta5d6ff"Ta5d6fftext-lg font-semiboldTa5d6ff"Tff7b72>
{{ Te6edf3$t("banishment.no_items") }}
Tff7b72</h3>
Tff7b72<p Te6edf3class=Ta5d6ff"Ta5d6ffmx-auto mt-1 max-w-sm text-sm text-sem-fg-mutedTa5d6ff"Tff7b72>
{{ searchQuery ? Te6edf3$t("nomadnet.no_search_results_peers") : Te6edf3$t("nomadnet.no_announces_yet") }}
Tff7b72</p>
Tff7b72</div>
Tff7b72<div v-else Te6edf3class=Ta5d6ff"Ta5d6ffgrid grid-cols-1 gap-3 py-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4Ta5d6ff"Tff7b72>
Tff7b72<div
Te6edf3v-for=Ta5d6ff"Ta5d6ffidentity in filteredBlockedIdentitiesTa5d6ff"
Te6edf3:key=Ta5d6ff"Ta5d6ffidentity.identity_hashTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffflex min-h-[9.5rem] min-w-0 flex-col rounded-xl bg-sem-surface/35 transition-colors hover:bg-sem-surface/55Ta5d6ff"
Te6edf3:class=Ta5d6ff"Ta5d6ff{
Ta5d6ff'Ta5d6ffring-1 ring-sem-accent/50Ta5d6ff'Ta5d6ff:
selectMode && selectedIdentities.includes(identity.identity_hash),
}Ta5d6ff"
Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex min-h-0 flex-1 flex-col gap-2.5 p-3Ta5d6ff"Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex min-w-0 items-start gap-2Ta5d6ff"Tff7b72>
Tff7b72<div Te6edf3v-if=Ta5d6ff"Ta5d6ffselectModeTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffflex shrink-0 items-center pt-0.5Ta5d6ff" @click.stopTff7b72>
Tff7b72<input
Te6edf3v-model=Ta5d6ff"Ta5d6ffselectedIdentitiesTa5d6ff"
Te6edf3type=Ta5d6ff"Ta5d6ffcheckboxTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffrounded border-sem-border text-sem-accent focus:ring-sem-accent/30Ta5d6ff"
Te6edf3:value=Ta5d6ff"Ta5d6ffidentity.identity_hashTa5d6ff"
Tff7b72/>
Tff7b72</div>
Tff7b72<div
Te6edf3class=Ta5d6ff"Ta5d6ffflex size-8 shrink-0 items-center justify-center rounded-full bg-sem-surface-mutedTa5d6ff"
Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffaccount-offTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffsize-4 text-red-500Ta5d6ff" Tff7b72/>
Tff7b72</div>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffmin-w-0 flex-1Ta5d6ff"Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffmb-1 flex min-w-0 items-center gap-1.5Ta5d6ff"Tff7b72>
Tff7b72<h3
Te6edf3class=Ta5d6ff"Ta5d6ffmin-w-0 flex-1 truncate text-sm font-semiboldTa5d6ff"
Te6edf3:title=Ta5d6ff"Ta5d6ffidentity.display_name || Te6edf3$tTa5d6ff(Ta5d6ff'Ta5d6ffcall.unknownTa5d6ff'Ta5d6ff)Ta5d6ff"
Tff7b72>
{{ identity.display_name || Te6edf3$t("call.unknown") }}
Tff7b72</h3>
Tff7b72<span
Te6edf3v-if=Ta5d6ff"Ta5d6ffidentity.is_nodeTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffshrink-0 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-sem-accentTa5d6ff"
Tff7b72>
{{ Te6edf3$t("banishment.node") }}
Tff7b72</span>
Tff7b72<span
v-else
Te6edf3class=Ta5d6ff"Ta5d6ffshrink-0 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-sem-fg-mutedTa5d6ff"
Tff7b72>
{{ Te6edf3$t("banishment.user") }}
Tff7b72</span>
Tff7b72</div>
Tff7b72<p
Te6edf3class=Ta5d6ff"Ta5d6fftruncate font-mono text-xs text-sem-fg-mutedTa5d6ff"
Te6edf3:title=Ta5d6ff"Ta5d6ffidentity.identity_hashTa5d6ff"
Tff7b72>
{{ identity.identity_hash }}
Tff7b72</p>
Tff7b72<p Te6edf3v-if=Ta5d6ff"Ta5d6ffidentityBlockedAt(identity)Ta5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffmt-0.5 text-[11px] text-sem-fg-mutedTa5d6ff"Tff7b72>
{{ Te6edf3$t("banishment.banished_at") }}
{{ formatTimeAgo(identityBlockedAt(identity)) }}
Tff7b72</p>
Tff7b72<span
Te6edf3v-if=Ta5d6ff"Ta5d6ffidentity.is_rns_blackholedTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffmt-1 inline-block truncate text-[10px] font-medium uppercase tracking-wide text-sem-fg-mutedTa5d6ff"
Te6edf3title=Ta5d6ff"Ta5d6ffBlackholed at Reticulum transport layerTa5d6ff"
Tff7b72>
RNS Blackhole
Tff7b72</span>
Tff7b72</div>
Tff7b72</div>
Tff7b72<div Te6edf3v-if=Ta5d6ff"Ta5d6ffidentity.blocked_destinations.length > 0Ta5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffmin-w-0Ta5d6ff"Tff7b72>
Tff7b72<p Te6edf3class=Ta5d6ff"Ta5d6ffmb-1 text-[10px] font-semibold uppercase tracking-wide text-sem-fg-mutedTa5d6ff"Tff7b72>
{{ Te6edf3$t("banishment.blocked_destinations") }}
Tff7b72</p>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffmax-h-20 space-y-1 overflow-y-autoTa5d6ff"Tff7b72>
Tff7b72<div
Te6edf3v-for=Ta5d6ff"Ta5d6ffdest in identity.blocked_destinationsTa5d6ff"
Te6edf3:key=Ta5d6ff"Ta5d6ffdest.destination_hashTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffflex min-w-0 items-center justify-between gap-2 rounded-md bg-sem-surface-muted/70 px-2 py-1 text-xsTa5d6ff"
Tff7b72>
Tff7b72<span
Te6edf3class=Ta5d6ff"Ta5d6ffmin-w-0 truncate font-mono text-sem-fg-mutedTa5d6ff"
Te6edf3:title=Ta5d6ff"Ta5d6ffdest.destination_hashTa5d6ff"
Tff7b72>
{{ dest.destination_hash }}
Tff7b72</span>
Tff7b72<span Te6edf3v-if=Ta5d6ff"Ta5d6ffdest.created_atTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffshrink-0 text-[10px] text-sem-fg-mutedTa5d6ff"Tff7b72>
{{ formatTimeAgo(dest.created_at) }}
Tff7b72</span>
Tff7b72</div>
Tff7b72</div>
Tff7b72</div>
Tff7b72<div
Te6edf3v-if=Ta5d6ff"Ta5d6ffidentity.rns_reasonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6fftruncate text-xs italic text-sem-fg-mutedTa5d6ff"
Te6edf3:title=Ta5d6ff"Ta5d6ffidentity.rns_reasonTa5d6ff"
Tff7b72>
Te6edf3“{{ identity.rns_reason }}Te6edf3”
Tff7b72</div>
Tff7b72<div
Te6edf3v-if=Ta5d6ff"Ta5d6ffidentity.rns_sourceTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6fftruncate font-mono text-[10px] text-sem-fg-mutedTa5d6ff"
Te6edf3:title=Ta5d6ff"Ta5d6ffidentity.rns_sourceTa5d6ff"
Tff7b72>
Source: {{ identity.rns_source }}
Tff7b72</div>
Tff7b72<button
Te6edf3v-if=Ta5d6ff"Ta5d6ff!selectModeTa5d6ff"
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffmt-auto inline-flex w-full items-center justify-center gap-1.5 rounded-full bg-sem-accent/15 px-3 py-2 text-sm font-medium text-sem-accent transition hover:bg-sem-accent/25Ta5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffonUnblock(identity)Ta5d6ff"
Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffcheck-circleTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffsize-4Ta5d6ff" Tff7b72/>
Tff7b72<spanTff7b72>{{ Te6edf3$t("banishment.lift_banishment") }}Tff7b72</span>
Tff7b72</button>
Tff7b72</div>
Tff7b72</div>
Tff7b72</div>
Tff7b72</div>
Tff7b72</div>
Tff7b72</div>
Tff7b72</template>
Tff7b72<scriptTff7b72>
import MaterialDesignIcon from "../MaterialDesignIcon.vue";
import DialogUtils from "../../js/DialogUtils";
import ToastUtils from "../../js/ToastUtils";
import Utils from "../../js/Utils";
export default {
name: "BlockedPage",
components: {
MaterialDesignIcon,
},
data() {
return {
blockedIdentities: {},
isLoading: false,
searchQuery: "",
selectMode: false,
selectedIdentities: [],
typeFilter: "all",
dateSort: "newest",
};
},
computed: {
allBlockedIdentities() {
return Object.values(this.blockedIdentities);
},
filteredBlockedIdentities() {
let list = [...this.allBlockedIdentities];
const query = this.searchQuery.trim().toLowerCase();
if (query) {
list = list.filter((identity) => {
if (identity.identity_hash.toLowerCase().includes(query)) return true;
if ((identity.display_name || "").toLowerCase().includes(query)) return true;
return identity.blocked_destinations.some((d) => d.destination_hash.toLowerCase().includes(query));
});
}
if (this.typeFilter === "user") {
list = list.filter((identity) => !identity.is_node && !identity.is_rns_blackholed);
} else if (this.typeFilter === "node") {
list = list.filter((identity) => identity.is_node);
} else if (this.typeFilter === "rns") {
list = list.filter((identity) => identity.is_rns_blackholed);
}
if (this.dateSort === "name") {
list.sort((a, b) => {
const nameA = (a.display_name || a.identity_hash).toLowerCase();
const nameB = (b.display_name || b.identity_hash).toLowerCase();
return nameA.localeCompare(nameB);
});
} else if (this.dateSort === "oldest") {
list.sort((a, b) => this.compareBlockedAt(a, b));
} else {
list.sort((a, b) => this.compareBlockedAt(b, a));
}
return list;
},
isAllSelected() {
if (this.filteredBlockedIdentities.length === 0) {
return false;
}
return this.filteredBlockedIdentities.every((identity) =>
this.selectedIdentities.includes(identity.identity_hash)
);
},
},
mounted() {
this.loadBlockedDestinations();
},
methods: {
async loadBlockedDestinations() {
this.isLoading = true;
try {
const response = await window.api.get("/api/v1/blocked-destinations");
const blockedHashes = response.data.blocked_destinations || [];
let reticulumBlackholed = {};
try {
const rnsResponse = await window.api.get("/api/v1/reticulum/blackhole");
reticulumBlackholed = rnsResponse.data.blackholed_identities || {};
} catch (e) {
console.error("Failed to load Reticulum blackhole", e);
}
const identityMap = {};
const ensureIdentity = (identityHash) => {
if (!identityMap[identityHash]) {
identityMap[identityHash] = {
identity_hash: identityHash,
display_name: null,
is_node: false,
blocked_destinations: [],
is_rns_blackholed: false,
rns_source: null,
rns_reason: null,
rns_until: null,
};
}
return identityMap[identityHash];
};
const processBlockedHash = async (blocked) => {
const hash = blocked.destination_hash;
let identityHash = hash;
let displayName = null;
let isNode = false;
try {
const announceResponse = await window.api.get("/api/v1/announces", {
params: {
destination_hash: hash,
include_blocked: true,
limit: 1,
},
});
if (announceResponse.data.announces && announceResponse.data.announces.length > 0) {
const announce = announceResponse.data.announces[0];
identityHash = announce.identity_hash || hash;
displayName = announce.display_name || null;
isNode = announce.aspect === "nomadnetwork.node";
}
} catch {
// ignore error
}
const identity = ensureIdentity(identityHash);
identity.display_name = identity.display_name || displayName;
identity.is_node = identity.is_node || isNode;
identity.blocked_destinations.push({
destination_hash: hash,
created_at: blocked.created_at || null,
});
};
await Promise.all(blockedHashes.map((blocked) => processBlockedHash(blocked)));
for (const [hash, info] of Object.entries(reticulumBlackholed)) {
const identity = ensureIdentity(hash);
identity.is_rns_blackholed = true;
identity.rns_source = info.source || null;
identity.rns_reason = info.reason || null;
identity.rns_until = info.until || null;
if (!identity.display_name) {
try {
const announceResponse = await window.api.get("/api/v1/announces", {
params: {
identity_hash: hash,
include_blocked: true,
limit: 1,
},
});
if (announceResponse.data.announces && announceResponse.data.announces.length > 0) {
const announce = announceResponse.data.announces[0];
identity.display_name = announce.display_name || null;
identity.is_node = announce.aspect === "nomadnetwork.node";
}
} catch {
// ignore
}
}
}
this.blockedIdentities = identityMap;
} catch (e) {
console.log(e);
ToastUtils.error(this.$t("banishment.failed_load_banished"));
} finally {
this.isLoading = false;
}
},
async onUnblock(identity) {
if (
!(await DialogUtils.confirm(
this.$t("banishment.lift_banishment_confirm", {
name: identity.display_name || identity.identity_hash,
})
))
) {
return;
}
try {
await this.unblockIdentity(identity);
await this.loadBlockedDestinations();
ToastUtils.success(this.$t("banishment.banishment_lifted"));
} catch (e) {
console.log(e);
ToastUtils.error(this.$t("banishment.failed_lift_banishment"));
}
},
async onUnblockSelected() {
if (this.selectedIdentities.length === 0) {
return;
}
if (
!(await DialogUtils.confirm(
this.$t("banishment.lift_selected_confirm", { count: this.selectedIdentities.length })
))
) {
return;
}
const selected = this.allBlockedIdentities.filter((identity) =>
this.selectedIdentities.includes(identity.identity_hash)
);
let lifted = 0;
for (const identity of selected) {
try {
await this.unblockIdentity(identity);
lifted += 1;
} catch (e) {
console.log(e);
}
}
this.exitSelectMode();
await this.loadBlockedDestinations();
if (lifted > 0) {
ToastUtils.success(this.$t("banishment.lift_selected_success", { count: lifted }));
}
if (lifted Tff7b72< selected.length) {
ToastUtils.error(this.$t("banishment.failed_lift_banishment"));
}
},
async unblockIdentity(identity) {
const Te6edf3targetHash =
Ta5d6ffidentity.blocked_destinations.length Tff7b72> 0
? identity.blocked_destinations[0].destination_hash
: identity.identity_hash;
await window.api.delete(`/api/v1/blocked-destinations/${targetHash}`);
},
toggleSelectAll() {
if (this.isAllSelected) {
this.selectedIdentities = [];
return;
}
this.selectedIdentities = this.filteredBlockedIdentities.map((identity) => identity.identity_hash);
},
exitSelectMode() {
this.selectMode = false;
this.selectedIdentities = [];
},
onSearchInput() {},
identityBlockedAt(identity) {
const dates = identity.blocked_destinations.map((dest) => dest.created_at).filter(Boolean);
if (dates.length === 0) {
return null;
}
return dates.sort().reverse()[0];
},
compareBlockedAt(a, b) {
const atA = this.identityBlockedAt(a) || "";
const atB = this.identityBlockedAt(b) || "";
if (atA === atB) {
const nameA = (a.display_name || a.identity_hash).toLowerCase();
const nameB = (b.display_name || b.identity_hash).toLowerCase();
return nameA.localeCompare(nameB);
}
return atA.localeCompare(atB);
},
formatTimeAgo(datetimeString) {
return Utils.formatTimeAgo(datetimeString);
},
},
};
Tff7b72</script>
Served by rngit 1.5.4 - Generated in 0.04s